package util;

import conf.AppConfig;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SecurityUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

    private static final char[] DICT = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray();
    public static String randomString(int length){
        StringBuffer res = new StringBuffer();
        for(int i=0; i<length; i++){
            res.append(""+DICT[(int)(Math.floor(Math.random()*DICT.length)-1)]);
        }
        return res.toString();
    }


    public static String base64Encode(byte[] source){
        return new String(Base64.getEncoder().encode(source));
    }

    public static byte[] base64Decode(String source){
        return Base64.getDecoder().decode(source);
    }

    public static String hexEncode(byte[] source){
        return new String(Hex.encode(source));
    }

    public static byte[] hexDecode(String source){
        return Hex.decode(source);
    }

    public static String md5(byte[] source){
        MD5Digest md5 = new MD5Digest();
        md5.update(source,0,source.length);
        byte[] md5Bytes = new byte[md5.getDigestSize()];
        md5.doFinal(md5Bytes, 0);
        return hexEncode(md5Bytes);
    }

    public static String sha1(byte[] source){
        SHA1Digest digest = new SHA1Digest();
        digest.update(source,0,source.length);
        byte[] tmpBytes = new byte[digest.getDigestSize()];
        digest.doFinal(tmpBytes, 0);
        return hexEncode(tmpBytes);
    }

    public static String sha256(byte[] source){
        SHA256Digest sha256Digest = new SHA256Digest();
        sha256Digest.update(source,0,source.length);
        byte[] tmpBytes = new byte[sha256Digest.getDigestSize()];
        sha256Digest.doFinal(tmpBytes, 0);
        return hexEncode(tmpBytes);
    }

    public static String aesCbcP7Encrypt(String keyStr, String ivStr, String source){
        try{
            IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes(AppConfig.SYS_CHARSET));
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyStr.getBytes(AppConfig.SYS_CHARSET), "AES"),iv);
            byte[] bytes = cipher.doFinal(source.getBytes(AppConfig.SYS_CHARSET));
            return base64Encode(bytes);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    public static String aesCbcP7Decrypt(String keyStr, String ivStr, byte[] source){
        try{
            IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes(AppConfig.SYS_CHARSET));
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyStr.getBytes(AppConfig.SYS_CHARSET), "AES"),iv);
            byte[] bytes = cipher.doFinal(source);
            return new String(bytes);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * privateKeyStr: pkcs8格式base64私钥字符窜
     * openssl默认生成的pkcs1格式
     *
     * ---PKCS1---↓
     * -----BEGIN RSA PRIVATE KEY-----
     * BASE64 Private Key String
     * -----END RSA PRIVATE KEY-----
     *
     * ---PKCS8---↓
     * -----BEGIN PRIVATE KEY-----
     * BASE64 Private Key String
     * -----END PRIVATE KEY-----
     *
     * 转换办法：openssl pkcs8 -topk8 -inform PEM -in pkcs1.pem -outform pem -nocrypt -out pkcs8.pem
     * */
    protected static PrivateKey getPrivateKey (String privateKeyStr) {
        PKCS8EncodedKeySpec keySpecPri = new PKCS8EncodedKeySpec(SecurityUtil.base64Decode(privateKeyStr));
        PrivateKey privateKey = null;
        try {
            KeyFactory keyFactoryPri = KeyFactory.getInstance("RSA");
            privateKey = keyFactoryPri.generatePrivate(keySpecPri);
        } catch (NoSuchAlgorithmException e) {
            //ignore
            logger.error("impossible", e);
        } catch (InvalidKeySpecException e){
            logger.error("The private key format is wrong.", e);
            throw new RuntimeException(e);
        }
        return privateKey;
    }

    /**
     * -----BEGIN PUBLIC KEY-----
     * publicKeyStr = (BASE64 Public Key String)
     * -----END PUBLIC KEY-----
     * */
    protected static PublicKey getPublicKey (String publicKeyStr) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64Decode(publicKeyStr));
        KeyFactory keyFactory = null;
        PublicKey publicKey = null;
        try {
            keyFactory = KeyFactory.getInstance("RSA");
            publicKey = keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            //ignore
            logger.error("impossible", e);
        } catch (InvalidKeySpecException e){
            logger.error("Public key format is incorrect.", e);
            throw new RuntimeException(e);
        }
        return publicKey;
    }

    public static String rsaPkcs1Encrypt(String publicKeyStr, String content){
        String res = "";
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));
            res = SecurityUtil.base64Encode(cipher.doFinal(content.getBytes(AppConfig.SYS_CHARSET)));
        } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
            //ignore
            logger.error("impossible", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            logger.error("Encryption failed.", e);
            throw new RuntimeException(e);
        }
        return res;
    }

    public static byte[] rsaPkcs1Decrypt(String privateKeyStr, String encryptData){
        byte[] res = null;
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));
            res = cipher.doFinal(SecurityUtil.base64Decode(encryptData));
        } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e)  {
            //ignore
            logger.error("impossible", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            logger.error("Decryption failure.", e);
            throw new RuntimeException(e);
        }
        return res;
    }

    private static final String DEFAULT_PARAMS_ENC_KEY = "/`Z*5%WtjxOx$Li8";
    public static final String paramsEncrypt(String key){
        return aesCbcP7Encrypt(DEFAULT_PARAMS_ENC_KEY, DEFAULT_PARAMS_ENC_KEY, key);
    }
    public static final String paramsDecrypt(String enc){
        return aesCbcP7Decrypt(DEFAULT_PARAMS_ENC_KEY, DEFAULT_PARAMS_ENC_KEY, base64Decode(enc));
    }
}
